﻿using System;
using RayDen.Library.Components.HashGrids;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Components;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Samplers;

namespace RayDen.RayEngine.Engines.ManyLights
{
    public class MLEngineContext
    {
        public static int MaxVpls = 10000;

        internal SurfaceSampler surfaceSampler;
        internal VplCollection vpls;
        private GenericHashGrid<Vpl> hashGrid;
        public int VplSamples = 16;

        #region VplSampler
        public class VplSampler : PathSamplingEngine<Vpl>
        {
            private VplCollection vpls;
            private IRandomProvider rnd;

            public VplSampler(VplCollection c, IRandomProvider rnd, IRayEngineScene scn, IIntersectionEngine ie)
                : base(scn, ie)
            {
                this.vpls = c;
                this.rnd = rnd;
            }

            protected override PathInfo GeneratePhoton(IRayEngineScene scn, IRandomProvider rnd)
            {
                float lPdf;
                LightSample lightSample;
                var scene = (RayEngineScene)scn;
                var light = scene.Lights[scene.SampleLights(rnd.NextFloat(), out lPdf)];
            reeval:
                light.EvaluatePhoton(scene, rnd.NextFloat(), rnd.NextFloat(), rnd.NextFloat(), rnd.NextFloat(),
                                     rnd.NextFloat(),
                                     out lightSample);
                if (lightSample.Pdf <= 0f)
                    goto reeval;
                var spd = new RgbSpectrum(lightSample.Spectrum.ToArray());
                return new PathInfo
                {
                    Ray = lightSample.LightRay,
                    RayIndex = -1,
                    BsdfWeight = 1.0f,
                    Data = new Vpl()
                        {
                            VplWeight = lightSample.Pdf * lPdf,
                        },
                    Throughput = (spd) / (lightSample.Pdf * lPdf)* MaxVpls
                };
            }

            protected override bool IsSamplingDone()
            {
                var done = vpls.Count >= MaxVpls;

                var cl = Console.CursorLeft;
                var ct = Console.CursorTop;
                Console.Write("{0}  ", vpls.Count);
                Console.SetCursorPosition(cl, ct);
                return done;
            }

            protected override bool OnSamplingDone()
            {
                return true;
            }


            protected override bool OnPathHit(ref Point hitPoint, ref Vector wo, ref Vector wi,
                                               SurfaceIntersectionData hitInfo, ref RgbSpectrum f, float fPdf, PathInfo photonPath)
            {
                if (f.IsBlack() || fPdf <= 0f)
                {
                    return false;
                }

                if (hitInfo.MMaterial.IsDiffuse() && photonPath.Depth > 0)
                {
                    var newVpl = new Vpl()
                        {
                            Bsdf = hitInfo.MMaterial,
                            ThroughtPut = photonPath.Throughput,
                            Spectrum = photonPath.Throughput,
                            HitPoint = hitPoint,
                            GeoNormal = hitInfo.Normal,
                            Wi = wi,
                            VplWeight = photonPath.BsdfWeight*photonPath.Data.VplWeight,
                        };
                    photonPath.Data = newVpl;
                    this.vpls.Add(newVpl);
                }

                float g = 1f;
                //if (this.vpls.Count > 2 & photonPath.Depth > 0)
                //{
                //    g = Geometry.G(ref vpls.Data[vpls.Count - 1].HitPoint, 
                //                   ref vpls.Data[vpls.Count - 2].HitPoint,
                //                   ref vpls.Data[vpls.Count - 1].GeoNormal,
                //                   ref vpls.Data[vpls.Count - 2].GeoNormal);
                //}

                photonPath.BsdfWeight *= fPdf;
                photonPath.Throughput *= (f / fPdf)*g;

                /*
                float continueProb = Math.Min(1f, 0.7f / photons.Throughput.y());
                if (rnd.NextFloat() > continueProb || photons.Depth > MaxPhotonDepth || photons.Throughput.IsBlack())
                {
                    return false;
                }

                photons.Throughput *= 0.7f / continueProb;
                */
                if (photonPath.Depth > MaxPhotonDepth)
                {
                    return false;
                }
                photonPath.Ray = new RayData(ref hitPoint, ref wi);
                return true;
            }
        }
        #endregion


        public MLEngineContext()
        {
            vpls = new VplCollection(MaxVpls);
        }

        public struct VplLightSample
        {
            public Vpl Light;
            public LightSample Ls;
        }

        public void BuildMap(IIntersectionEngine intersectionDevice, RayEngineScene scene)
        {

            IRandomProvider rnd = new FastRandom();
            var sampler = new VplSampler(vpls, rnd, scene, intersectionDevice) { SurfaceSampler = surfaceSampler };
            sampler.StartSampling();
            Tracer.TraceLine("Vpls collected. start building accelleration structure.");
            /*
            hashGrid = new GenericHashGrid<Vpl>(MaxVpls);
            foreach (var vpl in vpls.ToArray())
            {
                var hp = vpl.HitPoint;
                hashGrid.Store(ref hp, vpl);
            }
            hashGrid.ReHash();
            */
            Tracer.TraceLine("Complete ");

        }


    }
}
